#include <xy/ais/context.h>

namespace xy
{

namespace ais
{

using namespace xy::geom;

context::context(int width, int height)
    : m_mouseMsg(std::make_shared<msg_mouse>()), m_rendererFactory(std::make_shared<renderer_factory>())
{
    // 初始化相机数据
    m_mouseMsg->windowSize = vec2i{width, height};
    m_mouseMsg->camera = std::make_shared<camera>(10.0f, 90.0f, 0.0f, 45.0f, float(width) / height);
    m_mouseMsg->screenMapping = [this](const vec3f &pos) { return screen_mapping(pos); };
    m_mouseMsg->spaceMapping = [this](const vec2f &pos) { return space_mapping(pos); };
}

void context::mouse_wheel_event(float angleDelta)
{
    if (m_action == Action3d::None)
    {
        // auto distance = std::max(1.0f, m_mouseMsg->camera->distance() - angleDelta * m_scaleRate);
        // m_mouseMsg->camera->set_distance(distance);

        auto screen = m_mouseMsg->movePos;
        float d = -angleDelta * m_scaleRate;
        auto v = (m_mouseMsg->camera->position() - space_mapping(screen)).normalize();
        m_mouseMsg->camera->set_center(m_mouseMsg->camera->center() + v * d);
    }
}

void context::mouse_press_event(const vec2f &pos, msg_mouse::Button button)
{
    m_mouseMsg->depth = 1.0f;
    m_mouseMsg->button = button;
    m_mouseMsg->status = msg_mouse::Status::Press;
    m_mouseMsg->captured = nullptr;

    if (button == msg_mouse::Button::Middle)
        m_action = Action3d::Translate;
    else if (button == msg_mouse::Button::Right)
        m_action = Action3d::Rotate;
    else
    {
        m_mouseMsg->pressPos = pos;
        m_mouseMsg->pressed = nullptr;

        // 进行碰撞检测
        for (auto obj : m_objects)
            obj->handle_msg(m_mouseMsg);

        // 获得按下物体
        m_mouseMsg->pressed = m_mouseMsg->captured;
        if (m_mouseMsg->pressed)
            m_mouseMsg->pressed->set_status(object::Status::Pressed);
    }
}

void context::mouse_release_event(const vec2f &pos)
{
    m_action = Action3d::None;
    m_mouseMsg->depth = 1.0f;
    m_mouseMsg->status = msg_mouse::Status::Release;
    m_mouseMsg->captured = nullptr;

    if (m_mouseMsg->pressed)
    {
        m_mouseMsg->pressed->handle_msg(m_mouseMsg);
        m_mouseMsg->pressed = nullptr;
    }
}

void context::mouse_move_event(const vec2f &pos)
{
    // 记录之前的位置
    auto prevPos = m_mouseMsg->movePos;

    // 更新鼠标消息
    m_mouseMsg->depth = 1.0f;
    m_mouseMsg->movePos = pos;
    m_mouseMsg->status = msg_mouse::Status::Move;
    m_mouseMsg->captured = nullptr;

    switch (m_action)
    {
    case Action3d::Rotate: {
        // 注意要让画面朝 dv 方向移动，相机要朝 -dv 方向移动
        float dyaw = (prevPos.x() - pos.x()) * m_angleRate;
        float dpitch = (prevPos.y() - pos.y()) * m_angleRate;
        m_mouseMsg->camera->set_yaw(m_mouseMsg->camera->yaw() + dyaw);
        m_mouseMsg->camera->set_pitch(m_mouseMsg->camera->pitch() + dpitch);
        break;
    }
    case Action3d::Translate: {
        vec2f dv = prevPos - pos;
        vec3f newp =
            space_mapping(vec2f{dv.x() + m_mouseMsg->windowSize.x() / 2, dv.y() + m_mouseMsg->windowSize.y() / 2});
        vec3f n = (m_mouseMsg->camera->position() - newp).normalize();
        m_mouseMsg->camera->set_center(newp + n * dot(m_mouseMsg->camera->center() - newp, n));
        break;
    }
    default: {
        // 如果当前正在选中物体，就移动物体
        if (m_mouseMsg->pressed)
        {
            // 处理移动消息
            auto msg = std::make_shared<msg_move>();
            msg->pos = space_mapping(pos);
            msg->dir = (msg->pos - m_mouseMsg->camera->position()).normalize();
            m_mouseMsg->pressed->handle_msg(msg);
        }
        else
        {
            // 清除悬停物体
            m_mouseMsg->hovered = nullptr;

            // 进行碰撞检测
            for (auto obj : m_objects)
                obj->handle_msg(m_mouseMsg);

            // 获得悬停物体
            m_mouseMsg->hovered = m_mouseMsg->captured;
            if (m_mouseMsg->hovered)
                m_mouseMsg->hovered->set_status(object::Status::Hovered);
        }
        break;
    }
    }
}

void context::resize_event(int width, int height)
{
    m_mouseMsg->camera->set_aspect(float(width) / height);
    m_mouseMsg->windowSize = vec2i{width, height};
}

void context::key_event(int key)
{
}

void context::paint_event()
{
    auto msg = std::make_shared<msg_paint>();
    for (auto obj : m_objects)
        obj->handle_msg(msg);
}

void context::add_object(std::shared_ptr<object> obj)
{
    // 初始化渲染器，然后刷新
    auto init = std::make_shared<msg_init>();
    auto update = std::make_shared<msg_update>();
    init->factory = m_rendererFactory;

    obj->handle_msg(init);
    obj->handle_msg(update);
    m_objects.push_back(obj);
}

void context::register_renderer(std::string name, std::function<renderer *()> func)
{
    m_rendererFactory->register_product(name, func);
}

std::shared_ptr<msg_mouse> context::msg() const
{
    return m_mouseMsg;
}

vec3f context::space_mapping(const vec2f &pos) const
{
    // 计算屏幕点对应的空间点
    float x = pos.x() * 2.0f / m_mouseMsg->windowSize.x() - 1.0f;
    float y = -pos.y() * 2.0f / m_mouseMsg->windowSize.y() + 1.0f;

    // z 坐标越接近 1，得到的点越远离相机，反之越接近相机
    vec4f v{x, y, 0.8f, 1.0f};
    v = invert(m_mouseMsg->camera->projection() * m_mouseMsg->camera->view()).value() * v;
    return homogeneous_to_cartesian(v);
}

vec3f context::screen_mapping(const vec3f &pos) const
{
    vec4f p{pos.x(), pos.y(), pos.z(), 1.0f};
    vec4f q = m_mouseMsg->camera->projection() * m_mouseMsg->camera->view() * p;
    vec3f r;

    // 这个时候非常接近屏幕，将深度设为 1
    if (std::abs(q.w()) < std::numeric_limits<float>::epsilon())
        r.z() = 1.0f;
    else
    {
        q /= q.w();

        r.x() = (q.x() + 1.0f) * m_mouseMsg->windowSize.x() / 2.0f;
        r.y() = -(q.y() - 1.0f) * m_mouseMsg->windowSize.y() / 2.0f;
        r.z() = q.z();
    }
    return r;
}

} // namespace ais

} // namespace xy